/*
    Dominate 3D - A cube-based game of life

    Copyright (C) 2007, Craig H. Miller (kidmosey@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "cell.h"
#include "color.h"
#include "player.h"

#include <list>
#include <set>
#include <assert.h>

#ifdef _MSC_VER
#include <windows.h>
#endif
#include <GL/gl.h>

int Cell::s_id = 0;

Cell::Cell(const CVector3D &v1, const CVector3D &v2, const CVector3D &v3, const CVector3D &v4)
        : m_state(NOT_HIGHLIGHTED), m_player(&Player::nullPlayer)
{
    m_id = ++s_id;

    memset(m_neighbors, 0, sizeof(m_neighbors));

    m_verts[0] = v1;
    m_verts[1] = v2;
    m_verts[2] = v3;
    m_verts[3] = v4;
}

Cell::~Cell()
{
    m_id = 0;
    m_player = 0;
    m_state = NOT_HIGHLIGHTED;

    memset(m_neighbors, 0, sizeof(m_neighbors));
    memset(m_verts, 0, sizeof(m_verts));

    m_currentHighlight.clear();
}

CVector3D Cell::GetNormal() const
{
    return (m_verts[1]-m_verts[0]).cross(m_verts[3]-m_verts[0]).normal();
}

void Cell::Render() const
{
    //glLoadName((GLuint )this);
    glPushName((GLuint )this);

    GetPlayer()->Apply();

    // draw the quad
    glBegin(GL_QUADS);
        int i = 0;
        glVertex3f(m_verts[i].x, m_verts[i].y, m_verts[i].z);
        glTexCoord2f(0, 0);

        i++;
        glVertex3f(m_verts[i].x, m_verts[i].y, m_verts[i].z);
        glTexCoord2f(1, 0);

        i++;
        glVertex3f(m_verts[i].x, m_verts[i].y, m_verts[i].z);
        glTexCoord2f(1, 1);

        i++;
        glVertex3f(m_verts[i].x, m_verts[i].y, m_verts[i].z);
        glTexCoord2f(0, 1);
    glEnd();

    // draw the border
    DrawBorder(Color(0,0,0));

    glPopName();
}

void Cell::DrawBorder(const Color &c) const
{
    glColor4f(c.r, c.g, c.b, c.a);

    glBegin(GL_LINE_STRIP);
    for (int i = 0; i < 4; i++)
        glVertex3f(m_verts[i].x, m_verts[i].y, m_verts[i].z);

    glVertex3f(m_verts[0].x, m_verts[0].y, m_verts[0].z);
    glEnd();
}

int Cell::GetState() const
{
    return m_state;
}

const std::set<Cell *> &Cell::GetSurroundingCells() const
{
    return m_surroundingCells;
}

int Cell::CountSurroundingCells(Player *p) const
{
    assert(p);
    int result = 0;

    std::set<Cell *>::const_iterator i = m_surroundingCells.begin();
    while (i != m_surroundingCells.end())
    {
        assert(*i);

        if ((*i)->GetPlayer() == p)
            result++;

        *i++;
    }

    return result;
}

void Cell::InitSurroundingCells()
{
    m_surroundingCells.clear();

    assert(m_neighbors[0]);
    assert(m_neighbors[1]);
    assert(m_neighbors[2]);
    assert(m_neighbors[3]);

    for (int n = 0; n < 4; n++)
    {
        Cell *neighbor = m_neighbors[n];

        m_surroundingCells.insert(m_surroundingCells.end(), neighbor);

        for (int n2 = 0; n2 < 4; n2++)
        {
            Cell *neighborOfNeighbor = neighbor->m_neighbors[n2];
            assert(neighborOfNeighbor);

            if (neighborOfNeighbor != this)
                for (int v = 0; v < 4; v++)
                    if (this->Contains(neighborOfNeighbor->m_verts[v]))
                        m_surroundingCells.insert(m_surroundingCells.end(), neighborOfNeighbor);
        }
    }
}

int Cell::TakeOver(Player *p)
{
    assert(p);
    assert(m_neighbors[0]);
    assert(m_neighbors[1]);
    assert(m_neighbors[2]);
    assert(m_neighbors[3]);
    int result = 0;

    SetPlayer(p);

    std::set<Cell *>::iterator i = m_surroundingCells.begin();
    while (i != m_surroundingCells.end())
    {
        Cell *c = *i;
        assert(c);

        if (!c->IsEmpty() && c->GetPlayer() != p)
        {
            c->SetPlayer(p);
            result++;
        }
        *i++;
    }

    return result;
}

bool Cell::CanMove()
{
    InitHighlight();

    HighlightList::iterator i = m_currentHighlight.begin();
    while (i != m_currentHighlight.end())
    {
        assert((*i).first);

        if ((*i).first->IsEmpty())
            return true;

        *i++;
    }

    m_currentHighlight.clear();

    return false;
}

Cell::HighlightList &Cell::Select()
{
    InitHighlight();

    assert(m_currentHighlight.size());

    std::list<std::pair<Cell*,ESTATE> >::iterator i = m_currentHighlight.begin();
    while (i != m_currentHighlight.end())
    {
        assert((*i).first);

        (*i).first->m_state = (*i).second;
        *i++;
    }

    return m_currentHighlight;
}

void Cell::Unselect()
{
    std::list<std::pair<Cell*,ESTATE> >::iterator i = m_currentHighlight.begin();
    while (i != m_currentHighlight.end())
    {
        assert((*i).first);

        (*i).first->m_state = NOT_HIGHLIGHTED;
        *i++;
    }

    m_currentHighlight.clear();
}

bool Cell::IsEmpty() const
{
    return (GetPlayer() == &Player::nullPlayer);
}

void Cell::RenderHighlight() const
{
    //assert(m_currentHighlight.size());
    std::list<std::pair<Cell *, ESTATE> >::const_iterator i = m_currentHighlight.begin();
    while (i != m_currentHighlight.end())
    {
        assert((*i).first);

        Color c;
        glLineWidth(2);
        switch ((*i).second)
        {
        case SELECTED:
            glLineWidth(3);
            c = Color(1.0f,1.0f,1.0f);
            break;
        case JUMP_TO:
            c = Color(0.0f,0.5f,0.0f);
            break;
        case COPY_TO:
            c = Color(0.0f,1.0f,0.0f);
            break;
        default:
            break;
        }

        (*i).first->DrawBorder(c);
        *i++;
    }
    glLineWidth(2);
}

void Cell::SetNeighbor(int idx, Cell *val)
{
    assert(val != 0);
    assert(idx >= 0 && idx <= 5);

    m_neighbors[idx] = val;
}

std::vector<Cell *> Cell::Subdivide()
{
    std::vector<Cell *> result;

    CVector3D mid = (m_verts[0] + m_verts[2]) * 0.5f;
    CVector3D eMids[] =
    {
        (m_verts[0] + m_verts[1]) * 0.5f, // bottom
        (m_verts[1] + m_verts[2]) * 0.5f, // right
        (m_verts[2] + m_verts[3]) * 0.5f, // top
        (m_verts[3] + m_verts[0]) * 0.5f  // left
    };

    // bottom/left
    Cell *c = new Cell(m_verts[0], eMids[0], mid, eMids[3]);
    result.push_back(c);

    // bottom/right
    c = new Cell(eMids[0], m_verts[1], eMids[1], mid);
    result.push_back(c);

    // top/right
    c = new Cell(mid, eMids[1], m_verts[2], eMids[2]);
    result.push_back(c);

    // top/left
    c = new Cell(eMids[3], mid, eMids[2], m_verts[3]);
    result.push_back(c);

    return result;
}

bool Cell::Contains(const CVector3D &v)
{
    for (int i = 0; i < 4; i++)
        if (m_verts[i] == v)
            return true;

    return false;
}

Cell *Cell::GetNeighbor(int idx)
{
    assert(idx >= 0);
    assert(idx < 5);

    return m_neighbors[idx];
}

void Cell::SetPlayer(Player *player)
{
    assert(m_player);

    if (m_player != player)
    {
        m_player->m_total--;
        m_player = player;

        if (m_player == 0)
            m_player = &Player::nullPlayer;

        assert(m_player);
        m_player->m_total++;
    }
}

Player *Cell::GetPlayer() const
{
    assert(m_player);

    return m_player;
}

const CVector3D &Cell::GetVert(int idx)
{
    assert(idx >= 0 && idx < 4);

    return m_verts[idx];
}

void Cell::InitHighlight()
{
    // ORDER MATTERS!
    m_currentHighlight.clear();

    assert(m_neighbors[0]);
    assert(m_neighbors[1]);
    assert(m_neighbors[2]);
    assert(m_neighbors[3]);
    assert(m_neighbors[4]);

    // the opposite face
    m_currentHighlight.push_back(std::pair<Cell*,ESTATE>(m_neighbors[4], JUMP_TO));

    // setup the jump_to's
    for (int i = 0; i < 4; i++)
    {
        assert(m_neighbors[i]);
        assert(m_neighbors[i]->m_neighbors[i]);

        // the normals to compare.
        CVector3D n[] =
        {
            this->GetNormal(),
            m_neighbors[i]->GetNormal(),
            m_neighbors[i]->m_neighbors[i]->GetNormal()
        };

        // if we are on the same face
        // n[2] could point to n[0]'s normal in some circumstances,
        // so we need to check against both neighbors.
        if (n[0] == n[1] &&
            n[0] == n[2])
        {
            m_currentHighlight.push_back(std::pair<Cell*,ESTATE>(m_neighbors[i]->m_neighbors[i], JUMP_TO));
        }
    }

    // setup the copy_to's
    std::set<Cell *>::iterator i = m_surroundingCells.begin();
    while (i != m_surroundingCells.end())
    {
        m_currentHighlight.insert(m_currentHighlight.end(), std::pair<Cell*,ESTATE>(*i, COPY_TO));
        *i++;
    }

    // add this cell
    m_currentHighlight.push_back(std::pair<Cell*,ESTATE>(this, SELECTED));
}
